/**
 * Fichier contenant les fonctions outils CUDA
 */

#include "cuda_inc.h"

cudaEvent_t startEvent;
float elapsedTime;

/**
 * Permet de chronometrer des evenements device en millisecondes
 * @author cryptofredix
 * @param  action {CHRONO_START=demarrage du chrono, CHRONO_STOP=arret, 
 *         CHRONO_DISPLAY=affichage du temps}
 * @return le temps chronométré, STATUS_KO en cas d'erreur
 * @author fred
 * @todo   modif printf pour politique d'affichage plus pertinente
 */
float cuda_chrono(short action) {
  cudaEvent_t stopEvent = NULL;;
  switch(action) {
  case CHRONO_START:
    cudaEventCreate(&startEvent);
    cudaEventRecord(startEvent, 0);
    elapsedTime = 0;
    break;
  case CHRONO_STOP:
    cudaEventCreate(&stopEvent);
    cudaEventRecord(stopEvent, 0);
    cudaEventSynchronize(stopEvent);
    cudaEventElapsedTime(&elapsedTime, startEvent, stopEvent);
    cudaEventDestroy(startEvent);
    cudaEventDestroy(stopEvent);
    break;
  case CHRONO_DISPLAY:
    printf("elpased time : %f ms\n",elapsedTime);
    break;
  default:
    elapsedTime = STATUS_KO;
    break;
  }
  return elapsedTime;
}

#define MAX_INPUT_SIZE 16

/*
 * progress bar
 */
void progress_bar(int xx, int n, int w) {
  float ratio = xx/(float)n;
  int c = ratio*w;
  int x;
  printf("%3d%% [", (int)(ratio*100));
  for (x=0; x<c; x++)
    printf("=");
  for (x=0; x<w; x++)
    printf(" ");
  printf("]\n\033[F\033[J");
}

/**
 * recupère les informations sur les devices
 * @return le nombre de devices trouvées
 * @author fred
 */
int cuda_get_cuda_devices() {
  int devCount = 0;
  int device = 0;
  struct cudaDeviceProp deviceProp;
  
  cudaGetDeviceCount(&devCount);
  printf("found %d device(s)\n", devCount);

  for (device = 0; device < devCount; ++device) {
    cudaGetDeviceProperties(&deviceProp, device);
    printf("Device %d has compute capability %d.%d.\n",
	   device, deviceProp.major, deviceProp.minor);
    printf("Device %d has %d processors\n",
	   device, deviceProp.multiProcessorCount);
  }
  
  return devCount;
}

/**
 * chercher une correspondance d'empreinte md5 sur CUDA
 * @param to_hash          chaînes pour lesquelles il faut calculer l'empreinte
 * @param nb_elem_to_hash  nombre d'éléments à hacher
 * @param to_find          empreinte à trouver
 * @return indice de l'élément pour lequel on a trouvé une correspondance,
 *         -1 si erreur GPU
 *         -2 si pas de correspondances
 * @author fred
 */
int find_md5_checksum(unsigned char *to_hash, long nb_elem_to_hash, unsigned char *to_find) {
  int n,k,l,i;
  int ulk;
  int devid;
  int devcount;
  size_t pitch;
  unsigned char *host_array;
  unsigned char *device_array1;
  unsigned char *device_array2;
  cudaError_t err=0;
  int ret = 0;
  int found = 0;
  int indice = -2;

  devcount = cuda_get_cuda_devices();
  if (devcount == 0) {
    printf("Aucun matériel compatible CUDA trouvé\n");
    return -1;
  }

  /* allocations mémoires host et device */
  err = cudaMallocHost((void**)&host_array, 512*16*sizeof(unsigned char));
  if (err!=0) {
    printf("Erreur %d levée lors de cudaMallocHost\n", err);
    return -1;
  }
  err = cudaMalloc((void **)&device_array1, 512*16*sizeof(unsigned char));
  if (err!=0) {
    printf("Erreur %d levée lors de cudaMalloc\n", err);
    return -1;
  }
  err = cudaMalloc((void **)&device_array2, 512*16*sizeof(unsigned char));
  if (err!=0) {
    printf("Erreur %d levée lors de cudaMalloc\n", err);
    return -1;
  }

  if (nb_elem_to_hash%512)
    nb_elem_to_hash = nb_elem_to_hash/512+1;
  else
    nb_elem_to_hash = nb_elem_to_hash/512;

  for (l=0; l<nb_elem_to_hash && found!=1; l++) {
    memcpy(host_array, to_hash+(l*512*16), 512*16*sizeof(unsigned char));

    /* copie host -> device */
    err = cudaMemcpy(device_array1, host_array, 512*16*sizeof(unsigned char), cudaMemcpyHostToDevice);
    if (err!=0) {
      printf("Erreur %d levée lors de la copie vers le GPU\n", err);
      return -1;
    }

    /* exécution du kernel */
    void cudakernel(unsigned char* device_data, unsigned char* device_result);
    cudamd5kernel(device_array1, device_array2);
    /* copie device -> host */
    err = cudaMemcpy(host_array, device_array2, 512*16*sizeof(unsigned char), cudaMemcpyDeviceToHost);
    if (err!=0) {
      printf("Erreur %d levée lors de la copie vers le CPU\n", err);
      return -1;
    }

    /*
    printf("we have to find : ");
    for (n=0; n<16;n++)
      printf("%x-", to_find[n]);
    printf("\n");
    */
  
    for (ulk=0; ulk<512 && found!=1 ; ulk++) {
      ret = 1;
      for (n=0; n<16;n++) {
	if (host_array[ulk*16+n] != to_find[n]) {
	  ret = 0;
	  continue;
	}
	//printf("compare %x with %x (%d)\n", host_array[ulk*16+n], to_find[n], ret);
      }
      if (ret == 1) {
	found = 1;
	indice = ulk+l*512;
      }
    }
    progress_bar(l, nb_elem_to_hash, 20);
  }

  return indice;
}

/**
 * chercher une correspondance d'empreinte SHA1 sur CUDA
 * @param to_hash          chaînes pour lesquelles il faut calculer l'empreinte
 * @param nb_elem_to_hash  nombre d'éléments à hacher
 * @param to_find          empreinte à trouver
 * @return indice de l'élément pour lequel on a trouvé une correspondance,
 *         -1 si erreur GPU
 *         -2 si pas de correspondances
 * @author fred
 */
int find_SHA1_checksum(unsigned char *to_hash, long nb_elem_to_hash, unsigned char *to_find) {
  int n,k,l,i;
  int ulk;
  int devid;
  int devcount;
  size_t pitch;
  unsigned char *host_array1;
  unsigned char *host_array2;
  unsigned char *device_array1;
  unsigned char *device_array2;
  cudaError_t err=0;
  int ret = 0;
  int found = 0;
  int indice = -2;
  int jobs = 512;

  devcount = cuda_get_cuda_devices();
  if (devcount == 0) {
    printf("Aucun matériel compatible CUDA trouvé\n");
    return -1;
  }

  /* allocations mémoires host et device */
  err = cudaMallocHost((void**)&host_array1, jobs*16*sizeof(unsigned char));
  if (err!=0) {
    printf("Erreur %d levée lors de cudaMallocHost\n", err);
    return -1;
  }
  err = cudaMallocHost((void**)&host_array2, jobs*32*sizeof(unsigned char));
  if (err!=0) {
    printf("Erreur %d levée lors de cudaMallocHost\n", err);
    return -1;
  }
  err = cudaMalloc((void **)&device_array1, jobs*16*sizeof(unsigned char));
  if (err!=0) {
    printf("Erreur %d levée lors de cudaMalloc\n", err);
    return -1;
  }
  err = cudaMalloc((void **)&device_array2, jobs*32*sizeof(unsigned char));
  if (err!=0) {
    printf("Erreur %d levée lors de cudaMalloc\n", err);
    return -1;
  }

  if (nb_elem_to_hash%jobs)
    nb_elem_to_hash = nb_elem_to_hash/jobs+1;
  else
    nb_elem_to_hash = nb_elem_to_hash/jobs;

  for (l=0; l<nb_elem_to_hash && found!=1; l++) {
    memcpy(host_array1, to_hash+(l*jobs*16), jobs*16*sizeof(unsigned char));
    
    /* copie host -> device */
    err = cudaMemcpy(device_array1, host_array1, jobs*16*sizeof(unsigned char), cudaMemcpyHostToDevice);
    if (err!=0) {
      printf("Erreur %d levée lors de la copie vers le GPU\n", err);
      return -1;
    }

    /* exécution du kernel */
    void cudaSHA1kernel(unsigned char* device_data, unsigned char* device_result);
    cudaSHA1kernel(device_array1, device_array2);

    /* copie device -> host */
    err = cudaMemcpy(host_array2, device_array2, jobs*32*sizeof(unsigned char), cudaMemcpyDeviceToHost);
    if (err!=0) {
      printf("Erreur %d levée lors de la copie vers le CPU\n", err);
      return -1;
    }

    /*
    printf("we have to find : ");
    for (n=0; n<16;n++)
      printf("%x-", to_find[n]);
    printf("\n");
    */
    
    //a supprimer
    for (ulk=0; ulk<jobs && found!=1 ; ulk++) {
      printf("(%d):", ulk);
      for (n=0; n<20;n++) {
	printf("%x", host_array2[ulk*32+n]);
      }
      printf("\n");
    }

  
    for (ulk=0; ulk<jobs && found!=1 ; ulk++) {
      ret = 1;
      for (n=0; n<20;n++) {
	if (host_array2[ulk*32+n] != to_find[n]) {
	  ret = 0;
	  continue;
	}
	//printf("compare %x with %x (%d)\n", host_array[ulk*16+n], to_find[n], ret);
      }
      if (ret == 1) {
	found = 1;
	indice = ulk+l*jobs;
      }
    }
  }

  return indice;
}



